package com.anjubao.parking.municipal.common.aop;

import static org.slf4j.event.Level.ERROR;
import static org.slf4j.event.Level.WARN;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

import java.util.Optional;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

import com.anjubao.parking.municipal.common.dto.ApiCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpStatusCodeException;

import com.anjubao.parking.municipal.common.dto.ApiResult;
import com.anjubao.parking.municipal.common.exception.AbstractHttpException;
import com.anjubao.parking.municipal.common.exception.BadRequestException;

/**
 * Controller异常统一处理
 * 
 * @author chenfang
 */
@RestControllerAdvice
public class DefaultRestControllerExceptionHandler {
	private static final Logger logger = LoggerFactory.getLogger(DefaultRestControllerExceptionHandler.class);

	@ExceptionHandler(Throwable.class)
	public ResponseEntity<ApiResult<Void>> exception(HttpServletRequest request, Throwable ex) {
		return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, ex);
	}

	@ExceptionHandler({ HttpRequestMethodNotSupportedException.class, HttpMediaTypeException.class })
	public ResponseEntity<ApiResult<Void>> badRequest(HttpServletRequest request, ServletException ex) {
		return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, ex, WARN);
	}

	@ExceptionHandler(HttpStatusCodeException.class)
	public ResponseEntity<ApiResult<Void>> restTemplateException(HttpServletRequest request,
			HttpStatusCodeException ex) {
		return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, ex);
	}

	@ExceptionHandler(AccessDeniedException.class)
	public ResponseEntity<ApiResult<Void>> accessDeny(HttpServletRequest request, AccessDeniedException ex) {
		return handleError(request, ApiCode.FORBIDDEN, ex);
	}

	@ExceptionHandler({ AbstractHttpException.class })
	public ResponseEntity<ApiResult<Void>> badRequest(HttpServletRequest request, AbstractHttpException ex) {
		return handleError(request, ex.getApiCode(), ex);
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	public ResponseEntity<ApiResult<Void>> handleMethodArgumentNotValidException(HttpServletRequest request,
			MethodArgumentNotValidException ex) {
		final Optional<ObjectError> firstError = ex.getBindingResult().getAllErrors().stream().findFirst();
		if (firstError.isPresent()) {
			final String firstErrorMessage = firstError.get().getDefaultMessage();
			return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, new BadRequestException(firstErrorMessage));
		}
		return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, ex);
	}

	@ExceptionHandler(ConstraintViolationException.class)
	public ResponseEntity<ApiResult<Void>> handleConstraintViolationException(HttpServletRequest request,
			ConstraintViolationException ex) {
		return handleError(request, ApiCode.STATUS_INTERNAL_ERROR, new BadRequestException(ex.getMessage()));
	}

	private ResponseEntity<ApiResult<Void>> handleError(HttpServletRequest request, ApiCode status, Throwable ex) {
		return handleError(request, status, ex, ERROR);
	}

	private ResponseEntity<ApiResult<Void>> handleError(HttpServletRequest request, ApiCode status, Throwable ex,
			Level logLevel) {
		String message = ex.getMessage();
		printLog(message, ex, logLevel);

		ApiResult<Void> errorResult = new ApiResult<>(status.getCode(), message, null, ex.getClass().getName());

		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		return new ResponseEntity<>(errorResult, headers, HttpStatus.valueOf(200));
	}

	/**
	 * 打印日志, 其中logLevel为日志级别: ERROR/WARN/DEBUG/INFO/TRACE
	 * 
	 * @param message
	 * @param ex
	 * @param logLevel
	 * @author chenfang
	 */
	private void printLog(String message, Throwable ex, Level logLevel) {
		switch (logLevel) {
		case WARN:
			logger.warn(message, ex);
			break;
		case DEBUG:
			logger.debug(message, ex);
			break;
		case INFO:
			logger.info(message, ex);
			break;
		case TRACE:
			logger.trace(message, ex);
			break;
		default:
			logger.error(message, ex);
			break;
		}
	}
}